La idea de “datos ordenados” o tidy data es una forma de manejar de forma efectiva y eso aplica también para el caso de los datos textuales. Según Hadley Wickham (2004) los datos “tidy” tiene tres características:

(Si en esta definición les resuena aquello que en las materias de Metodología de la Investigación se llamaba “estructura tripartita del dato” o lo que Juan Samaja llamaba “estructura cuatriparti ta del dato” están bien encaminades).

En el contexto de minería de texto, los datos ordenados van a tener la siguente estrucutra: un token por fila. Un token es una unidad conceptual y/o analíticamente signifactivas con las que dividimos un documento. Un token puede ser una palabra (ese será el caso más frecuente en este curso) pero también, n-gramas, oraciones e incluso párrafos. De hecho, un primer paso en el preprocesamiento de texto es dividir el corpus en tokens.

Como puede verse esta estructura difiere de otras formas de almacenar el texto crudo

Como iremos viendo, va a ser muy fácil pasar del texto en formato tidy a otros formatos. Particularmente, vamos a estar yendo y viniendo para diferentes tareas. Así, tendremos que modelar en formato Matrix-documento-término, pero llevaremos esos resultados a formato tidy para generar visualizaciones que nos permitan interpretar este modelo.

Primer ejemplo

Marx escribió en el famoso Prólogo a la Contribución a la Crítica de la Economía Política de 1859:

marx <- c("El conjunto de estas relaciones de producción forma la estructura económica de la sociedad, la base real sobre la que se levanta la superestructura jurídica y política y a la que corresponden determinadas formas de conciencia social. El modo de producción de la vida material condiciona el proceso de la vida social política y espiritual en general. No es la conciencia del hombre la que determina su ser sino, por el contrario, el ser social es lo que determina su conciencia. Al llegar a una fase determinada de desarrollo las fuerzas productivas materiales de la sociedad entran en contradicción con las relaciones de producción existentes o, lo que no es más que la expresión jurídica de esto, con las relaciones de propiedad dentro de las cuales se han desenvuelto hasta allí. De formas de desarrollo de las fuerzas productivas, estas relaciones se convierten en trabas suyas, y se abre así una época de revolución social. Al cambiar la base económica se transforma, más o menos rápidamente, toda la inmensa superestructura erigida sobre ella.")

marx
[1] "El conjunto de estas relaciones de producción forma la estructura económica de la sociedad, la base real sobre la que se levanta la superestructura jurídica y política y a la que corresponden determinadas formas de conciencia social. El modo de producción de la vida material condiciona el proceso de la vida social política y espiritual en general. No es la conciencia del hombre la que determina su ser sino, por el contrario, el ser social es lo que determina su conciencia. Al llegar a una fase determinada de desarrollo las fuerzas productivas materiales de la sociedad entran en contradicción con las relaciones de producción existentes o, lo que no es más que la expresión jurídica de esto, con las relaciones de propiedad dentro de las cuales se han desenvuelto hasta allí. De formas de desarrollo de las fuerzas productivas, estas relaciones se convierten en trabas suyas, y se abre así una época de revolución social. Al cambiar la base económica se transforma, más o menos rápidamente, toda la inmensa superestructura erigida sobre ella."

¿Qué formato de los que vimos hasta aquí sería este?

Para poder analizarlo como datos tidy, primero tenemos que llevarlo a un dataframe.

marx_df <- tibble(line = 1, text = marx)
marx_df

¿Qué significa que este marco de datos se ha impreso como un “tibble”? Un tibble es una clase moderna de marco de datos dentro de R, disponible en los paquetes dplyr y tibble, que tiene un método de impresión conveniente, no convierte cadenas en factores y no usa nombres de fila. Tibbles son ideales para usar con herramientas ordenadas.

Sin embargo, tenga en cuenta que este marco de datos que contiene texto aún no es compatible con un análisis de texto ordenado. No podemos filtrar las palabras ni contar las que ocurren con mayor frecuencia, ya que cada fila está formada por varias palabras combinadas. Necesitamos convertir esto para que tenga un token por documento por fila.

¿Cuántos documentos tenemos?

Dentro de nuestro marco de texto ordenado, necesitamos dividir el texto en tokens individuales (un proceso llamado tokenización) y transformarlo en una estructura de datos ordenada. Para hacer esto, usamos la función unnest_tokens() de tidytext.

library(tidytext)

marx_df %>%
  unnest_tokens(word, text)

Usamos aquí dos argumentos básicos:

Recordar que text_df arriba tiene una columna llamada text que contiene los datos de interés. A su vez unnest_tokens() realiza la tokenización por defecto usando palabras. Esto puede cambiarse sin problemas.

¿Qué formato tiene ahora?

A su vez, es importante observar que:

Un diagrama del flujo de trabajo puede verse a continuación:

Ordenando algunos textos de Marx y Engels

Vamos a trabajar con un dataset que el capo de Diego Kosloski escrapeó de la sección en español del Marxist Internet Archive.

Cargamos los datos:

marx_engels <- read_csv('../data/marx_engels.csv')
Parsed with column specification:
cols(
  tipo = col_character(),
  autor = col_character(),
  titulo = col_character(),
  texto = col_character()
)
marx_engels

¿Qué estructura tiene este dataset?

Vamos a transformarlo en un formato tidy:

marx_engels_tidy

Eliminando stopwords

El siguiente paso es la eliminación de las llamadas stopwords. Se trata de palabras que o bien por su función sintáctica (pronombres, preposiciones, adverbios, etc.) o por su frecuencia (aparecen en gran frecuencia) no aportan información al texto.

En general, la forma estandar de lidiar con las stopwords es mediante su eliminación a través de una lista. Carguemos la lista con las stopwords, al mismo tiempo, vamos a eliminar los acentos de esta tabla.

stop_words <- read_csv('https://raw.githubusercontent.com/Alir3z4/stop-words/master/spanish.txt', col_names=FALSE) %>%
        rename(word = X1) %>%
        mutate(word = stringi::stri_trans_general(word, "Latin-ASCII"))
Parsed with column specification:
cols(
  X1 = col_character()
)

Ahora sí, podemos removerlas usando la funcion anti_join

marx_engels_tidy <- marx_engels_tidy %>%
  anti_join(stop_words)
Joining, by = "word"

Fíjense cómo pasamos de aprimadamente 1.000.000 de palabras a 529.000 luego de eliminar las stop_words.

Ahora bien, ¿cuáles son las palabras más usadas por Marx y Engels?

marx_engels_tidy %>%
        count(word, sort=TRUE)

Actividad

Escribir el código para replicar la tabla anterior usando los comandos del tidyverse

###

Debido a que hemos estado usando herramientas ordenadas, nuestros recuentos de palabras se almacenan en un marco de datos ordenado. Esto nos permite canalizar esto directamente al paquete ggplot2, por ejemplo, para crear una visualización de las palabras más comunes:

marx_engels_tidy %>%
        count(word, sort=TRUE) %>%
        filter(n > 600) %>%
        mutate(word = reorder(word, n)) %>%
        ggplot(aes(n, word)) +
                geom_col() +
                labs(y = NULL)

Podríamos evaluar ahora si Marx y Engels usan diferentes palabras en las cargas y notas y en sus libros. Para ello vamos a tener que procesar un poco el campo de titulo:

marx_engels_tidy <- marx_engels_tidy %>%
        mutate(tipo = case_when(
                str_detect(titulo, 'Carta') ~ 'cartas',
                TRUE ~ tipo
        )) 

Utilizamos la función str_detect del paquete stringr para testear si la condición se cumple… en este caso si la palabra Carta aparece en las filas de título.

freqs <- marx_engels_tidy %>%
        mutate(word = str_extract(word, "[a-z']+")) %>%
        group_by(tipo, word) %>%
        summarise(n = n()) %>%
        mutate(
                total = sum(n),
                prop = n/total*100) %>%
        select(tipo, word, prop) %>%
        pivot_wider(names_from = tipo, values_from = prop)
`summarise()` has grouped output by 'tipo'. You can override using the `.groups` argument.
freqs

Y ahora podemos hacer un gráfico en el que comparamos la frecuencia de uso de las diferentes palabras en los libros y las notas:

freqs %>%
ggplot( aes(notas, libros)) +
  geom_jitter(alpha = 0.05, size = 2.5, width = 0.25, height = 0.25) +
  geom_text(aes(label = word), check_overlap = TRUE, vjust = 1.5) +
  scale_x_log10() +
  scale_y_log10() +
  geom_abline(color = "red") +
  theme_minimal()

Las palabras que están cerca de la línea en estos gráficos tienen frecuencias similares en ambos conjuntos de textos, por ejemplo, tanto en los libros de Marx y Engels como en las aparecen con frecuencias simialres: burguesa, acción, campesinos, abolición, comuna, producción, clase. En cambio, en los libros parecen aparecer palabras como esencia, concepto, hegel, indidiuo, fenomenología, misterio. En las notas periodísitcas aparece notablemente palabras ligadas a la acción política: congreso, consejo, internacional, liga, estatutos, etc.


Actividad

Repetir el ejercicio comparando las cartas con los libros

###

LS0tCnRpdGxlOiAiQ2xhc2UgMS4gQ29uY2VwdG8gZGUgZGF0b3Mgb3JkZW5hZG9zIHBhcmEgbGEgbWluZXLDrWEgZGUgdGV4dG8iCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCmBgYHtyIGluY2x1ZGU9RkFMU0V9CmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KHRpZHl0ZXh0KQpgYGAKCkxhIGlkZWEgZGUgImRhdG9zIG9yZGVuYWRvcyIgbyBfdGlkeSBkYXRhXyBlcyB1bmEgZm9ybWEgZGUgbWFuZWphciBkZSBmb3JtYSBlZmVjdGl2YSB5IGVzbyBhcGxpY2EgdGFtYmnDqW4gcGFyYSBlbCBjYXNvIGRlIGxvcyBkYXRvcyB0ZXh0dWFsZXMuIFNlZ8O6biBbSGFkbGV5IFdpY2toYW0gKDIwMDQpXSgpIGxvcyBkYXRvcyAidGlkeSIgdGllbmUgdHJlcyBjYXJhY3RlcsOtc3RpY2FzOgoKLSBDYWRhIHZhcmlhYmxlIChvIGF0cmlidXRvKSBlcyB1bmEgY29sdW1uYQotIENhZGEgdW5pZGFkICh1IG9ic2VydmFjacOzbikgZXMgdW5hIGZpbGEKLSBDYWRhIHRpcG8gZGUgdW5pZGFkIG9ic2VydmFjaW9uYWwgZXMgdW5hIHRhYmxhCgooU2kgZW4gZXN0YSBkZWZpbmljacOzbiBsZXMgcmVzdWVuYSBhcXVlbGxvIHF1ZSBlbiBsYXMgbWF0ZXJpYXMgZGUgTWV0b2RvbG9nw61hIGRlIGxhIEludmVzdGlnYWNpw7NuIHNlIGxsYW1hYmEgImVzdHJ1Y3R1cmEgdHJpcGFydGl0YSBkZWwgZGF0byIgbyBsbyBxdWUgSnVhbiBTYW1hamEgbGxhbWFiYSAiZXN0cnVjdHVyYSBjdWF0cmlwYXJ0aQp0YSBkZWwgZGF0byIgZXN0w6FuIGJpZW4gZW5jYW1pbmFkZXMpLgoKRW4gZWwgY29udGV4dG8gZGUgbWluZXLDrWEgZGUgdGV4dG8sIGxvcyBkYXRvcyBvcmRlbmFkb3MgdmFuIGEgdGVuZXIgbGEgc2lndWVudGUgZXN0cnVjdXRyYTogdW4gKip0b2tlbioqIHBvciBmaWxhLiBVbiAqKnRva2VuKiogZXMgdW5hIHVuaWRhZCBjb25jZXB0dWFsIHkvbyBhbmFsw610aWNhbWVudGUgc2lnbmlmYWN0aXZhcyBjb24gbGFzIHF1ZSBkaXZpZGltb3MgdW4gZG9jdW1lbnRvLiBVbiAqKnRva2VuKiogcHVlZGUgc2VyIHVuYSBwYWxhYnJhIChlc2Ugc2Vyw6EgZWwgY2FzbyBtw6FzIGZyZWN1ZW50ZSBlbiBlc3RlIGN1cnNvKSBwZXJvIHRhbWJpw6luLCBbbi1ncmFtYXNdKGh0dHBzOi8vZXMud2lraXBlZGlhLm9yZy93aWtpL04tZ3JhbWEpLCBvcmFjaW9uZXMgZSBpbmNsdXNvIHDDoXJyYWZvcy4gRGUgaGVjaG8sIHVuIHByaW1lciBwYXNvIGVuIGVsIHByZXByb2Nlc2FtaWVudG8gZGUgdGV4dG8gZXMgZGl2aWRpciBlbCBjb3JwdXMgZW4gKip0b2tlbnMqKi4KCkNvbW8gcHVlZGUgdmVyc2UgZXN0YSBlc3RydWN0dXJhIGRpZmllcmUgZGUgb3RyYXMgZm9ybWFzIGRlIGFsbWFjZW5hciBlbCB0ZXh0byBjcnVkbwoKLSBDYWRlbmE6IGVsIHRleHRvIHB1ZWRlLCBwb3Igc3VwdWVzdG8sIGFsbWFjZW5hcnNlIGNvbW8gY2FkZW5hcywgZXMgZGVjaXIsIHZlY3RvcmVzIGRlIGNhcmFjdGVyZXMsIGRlbnRybyBkZSBSIHksIGEgbWVudWRvLCBsb3MgZGF0b3MgZGUgdGV4dG8gc2UgbGVlbiBwcmltZXJvIGVuIGxhIG1lbW9yaWEgZGUgZXN0YSBmb3JtYS4KLSBDb3JwdXM6IGVzdG9zIHRpcG9zIGRlIG9iamV0b3Mgc3VlbGVuIGNvbnRlbmVyIGNhZGVuYXMgc2luIHByb2Nlc2FyIGFub3RhZGFzIGNvbiBtZXRhZGF0b3MgeSBkZXRhbGxlcyBhZGljaW9uYWxlcy4KLSBNYXRyaXogZG9jdW1lbnRvLXTDqXJtaW5vOiBlc3RhIGVzIHVuYSBtYXRyaXogZGlzcGVyc2EgcXVlIGRlc2NyaWJlIHVuYSBjb2xlY2Npw7NuIChlcyBkZWNpciwgdW4gY29ycHVzKSBkZSBkb2N1bWVudG9zIGNvbiB1bmEgZmlsYSBwYXJhIGNhZGEgZG9jdW1lbnRvIHkgdW5hIGNvbHVtbmEgcGFyYSBjYWRhIHTDqXJtaW5vLiBFbCB2YWxvciBkZSBsYSBtYXRyaXogc3VlbGUgc2VyIGVsIHJlY3VlbnRvIGRlIHBhbGFicmFzIG8gdGYtaWRmLgoKQ29tbyBpcmVtb3MgdmllbmRvLCB2YSBhIHNlciBtdXkgZsOhY2lsIHBhc2FyIGRlbCB0ZXh0byBlbiBmb3JtYXRvIHRpZHkgYSBvdHJvcyBmb3JtYXRvcy4gUGFydGljdWxhcm1lbnRlLCB2YW1vcyBhIGVzdGFyIHllbmRvIHkgdmluaWVuZG8gcGFyYSBkaWZlcmVudGVzIHRhcmVhcy4gQXPDrSwgdGVuZHJlbW9zIHF1ZSBtb2RlbGFyIGVuIGZvcm1hdG8gTWF0cml4LWRvY3VtZW50by10w6lybWlubywgcGVybyBsbGV2YXJlbW9zIGVzb3MgcmVzdWx0YWRvcyBhIGZvcm1hdG8gdGlkeSBwYXJhIGdlbmVyYXIgdmlzdWFsaXphY2lvbmVzIHF1ZSBub3MgcGVybWl0YW4gaW50ZXJwcmV0YXIgZXN0ZSBtb2RlbG8uCgojIyBQcmltZXIgZWplbXBsbwoKTWFyeCBlc2NyaWJpw7MgZW4gZWwgZmFtb3NvIFByw7Nsb2dvIGEgbGEgQ29udHJpYnVjacOzbiBhIGxhIENyw610aWNhIGRlIGxhIEVjb25vbcOtYSBQb2zDrXRpY2EgZGUgMTg1OToKCgpgYGB7cn0KbWFyeCA8LSBjKCJFbCBjb25qdW50byBkZSBlc3RhcyByZWxhY2lvbmVzIGRlIHByb2R1Y2Npw7NuIGZvcm1hIGxhIGVzdHJ1Y3R1cmEgZWNvbsOzbWljYSBkZSBsYSBzb2NpZWRhZCwgbGEgYmFzZSByZWFsIHNvYnJlIGxhIHF1ZSBzZSBsZXZhbnRhIGxhIHN1cGVyZXN0cnVjdHVyYSBqdXLDrWRpY2EgeSBwb2zDrXRpY2EgeSBhIGxhIHF1ZSBjb3JyZXNwb25kZW4gZGV0ZXJtaW5hZGFzIGZvcm1hcyBkZSBjb25jaWVuY2lhIHNvY2lhbC4gRWwgbW9kbyBkZSBwcm9kdWNjacOzbiBkZSBsYSB2aWRhIG1hdGVyaWFsIGNvbmRpY2lvbmEgZWwgcHJvY2VzbyBkZSBsYSB2aWRhIHNvY2lhbCBwb2zDrXRpY2EgeSBlc3Bpcml0dWFsIGVuIGdlbmVyYWwuIE5vIGVzIGxhIGNvbmNpZW5jaWEgZGVsIGhvbWJyZSBsYSBxdWUgZGV0ZXJtaW5hIHN1IHNlciBzaW5vLCBwb3IgZWwgY29udHJhcmlvLCBlbCBzZXIgc29jaWFsIGVzIGxvIHF1ZSBkZXRlcm1pbmEgc3UgY29uY2llbmNpYS4gQWwgbGxlZ2FyIGEgdW5hIGZhc2UgZGV0ZXJtaW5hZGEgZGUgZGVzYXJyb2xsbyBsYXMgZnVlcnphcyBwcm9kdWN0aXZhcyBtYXRlcmlhbGVzIGRlIGxhIHNvY2llZGFkIGVudHJhbiBlbiBjb250cmFkaWNjacOzbiBjb24gbGFzIHJlbGFjaW9uZXMgZGUgcHJvZHVjY2nDs24gZXhpc3RlbnRlcyBvLCBsbyBxdWUgbm8gZXMgbcOhcyBxdWUgbGEgZXhwcmVzacOzbiBqdXLDrWRpY2EgZGUgZXN0bywgY29uIGxhcyByZWxhY2lvbmVzIGRlIHByb3BpZWRhZCBkZW50cm8gZGUgbGFzIGN1YWxlcyBzZSBoYW4gZGVzZW52dWVsdG8gaGFzdGEgYWxsw60uIERlIGZvcm1hcyBkZSBkZXNhcnJvbGxvIGRlIGxhcyBmdWVyemFzIHByb2R1Y3RpdmFzLCBlc3RhcyByZWxhY2lvbmVzIHNlIGNvbnZpZXJ0ZW4gZW4gdHJhYmFzIHN1eWFzLCB5IHNlIGFicmUgYXPDrSB1bmEgw6lwb2NhIGRlIHJldm9sdWNpw7NuIHNvY2lhbC4gQWwgY2FtYmlhciBsYSBiYXNlIGVjb27Ds21pY2Egc2UgdHJhbnNmb3JtYSwgbcOhcyBvIG1lbm9zIHLDoXBpZGFtZW50ZSwgdG9kYSBsYSBpbm1lbnNhIHN1cGVyZXN0cnVjdHVyYSBlcmlnaWRhIHNvYnJlIGVsbGEuIikKCm1hcngKYGBgCgrCv1F1w6kgZm9ybWF0byBkZSBsb3MgcXVlIHZpbW9zIGhhc3RhIGFxdcOtIHNlcsOtYSBlc3RlPwoKUGFyYSBwb2RlciBhbmFsaXphcmxvIGNvbW8gZGF0b3MgdGlkeSwgcHJpbWVybyB0ZW5lbW9zIHF1ZSBsbGV2YXJsbyBhIHVuIGRhdGFmcmFtZS4KCmBgYHtyfQptYXJ4X2RmIDwtIHRpYmJsZShsaW5lID0gMSwgdGV4dCA9IG1hcngpCm1hcnhfZGYKYGBgCsK/UXXDqSBzaWduaWZpY2EgcXVlIGVzdGUgbWFyY28gZGUgZGF0b3Mgc2UgaGEgaW1wcmVzbyBjb21vIHVuICJ0aWJibGUiPyBVbiB0aWJibGUgZXMgdW5hIGNsYXNlIG1vZGVybmEgZGUgbWFyY28gZGUgZGF0b3MgZGVudHJvIGRlIFIsIGRpc3BvbmlibGUgZW4gbG9zIHBhcXVldGVzIGRwbHlyIHkgdGliYmxlLCBxdWUgdGllbmUgdW4gbcOpdG9kbyBkZSBpbXByZXNpw7NuIGNvbnZlbmllbnRlLCBubyBjb252aWVydGUgY2FkZW5hcyBlbiBmYWN0b3JlcyB5IG5vIHVzYSBub21icmVzIGRlIGZpbGEuIFRpYmJsZXMgc29uIGlkZWFsZXMgcGFyYSB1c2FyIGNvbiBoZXJyYW1pZW50YXMgb3JkZW5hZGFzLgoKU2luIGVtYmFyZ28sIHRlbmdhIGVuIGN1ZW50YSBxdWUgZXN0ZSBtYXJjbyBkZSBkYXRvcyBxdWUgY29udGllbmUgdGV4dG8gYcO6biBubyBlcyBjb21wYXRpYmxlIGNvbiB1biBhbsOhbGlzaXMgZGUgdGV4dG8gb3JkZW5hZG8uIE5vIHBvZGVtb3MgZmlsdHJhciBsYXMgcGFsYWJyYXMgbmkgY29udGFyIGxhcyBxdWUgb2N1cnJlbiBjb24gbWF5b3IgZnJlY3VlbmNpYSwgeWEgcXVlIGNhZGEgZmlsYSBlc3TDoSBmb3JtYWRhIHBvciB2YXJpYXMgcGFsYWJyYXMgY29tYmluYWRhcy4gTmVjZXNpdGFtb3MgY29udmVydGlyIGVzdG8gcGFyYSBxdWUgdGVuZ2EgdW4gdG9rZW4gcG9yIGRvY3VtZW50byBwb3IgZmlsYS4KCsK/Q3XDoW50b3MgZG9jdW1lbnRvcyB0ZW5lbW9zPwoKRGVudHJvIGRlIG51ZXN0cm8gbWFyY28gZGUgdGV4dG8gb3JkZW5hZG8sIG5lY2VzaXRhbW9zIGRpdmlkaXIgZWwgdGV4dG8gZW4gdG9rZW5zIGluZGl2aWR1YWxlcyAodW4gcHJvY2VzbyBsbGFtYWRvIHRva2VuaXphY2nDs24pIHkgdHJhbnNmb3JtYXJsbyBlbiB1bmEgZXN0cnVjdHVyYSBkZSBkYXRvcyBvcmRlbmFkYS4gUGFyYSBoYWNlciBlc3RvLCB1c2Ftb3MgbGEgZnVuY2nDs24gYHVubmVzdF90b2tlbnMoKWAgZGUgdGlkeXRleHQuCgoKYGBge3J9CmxpYnJhcnkodGlkeXRleHQpCgptYXJ4X2RmICU+JQogIHVubmVzdF90b2tlbnMod29yZCwgdGV4dCkKYGBgCgpVc2Ftb3MgYXF1w60gZG9zIGFyZ3VtZW50b3MgYsOhc2ljb3M6CgotIGVsIG5vbWJyZSBkZSBsYSBjb2x1bW5hIGRlIHNhbGlkYSBxdWUgc2UgY3JlYXLDoSBjdWFuZG8gZWwgdGV4dG8gbm8gZXN0w6kgYW5pZGFkbyAocGFsYWJyYSwgZW4gZXN0ZSBjYXNvKSwgeSBsdWVnbyAKLSBsYSBjb2x1bW5hIGRlIGVudHJhZGEgZGUgbGEgcXVlIHByb3ZpZW5lIGVsIHRleHRvICh0ZXh0bywgZW4gZXN0ZSBjYXNvKS4gCgpSZWNvcmRhciBxdWUgYHRleHRfZGZgIGFycmliYSB0aWVuZSB1bmEgY29sdW1uYSBsbGFtYWRhIGB0ZXh0YCBxdWUgY29udGllbmUgbG9zIGRhdG9zIGRlIGludGVyw6lzLiBBIHN1IHZleiBgdW5uZXN0X3Rva2VucygpYCByZWFsaXphIGxhIHRva2VuaXphY2nDs24gcG9yIGRlZmVjdG8gdXNhbmRvIHBhbGFicmFzLiBFc3RvIHB1ZWRlIGNhbWJpYXJzZSBzaW4gcHJvYmxlbWFzLgoKwr9RdcOpIGZvcm1hdG8gdGllbmUgYWhvcmE/CgpBIHN1IHZleiwgZXMgaW1wb3J0YW50ZSBvYnNlcnZhciBxdWU6CgotIFNlIGNvbnNlcnZhbiBvdHJhcyBjb2x1bW5hcywgY29tbyBlbCBuw7ptZXJvIGRlIGzDrW5lYSBkZSBkb25kZSBwcm92aWVuZSBjYWRhIHBhbGFicmEuCi0gU2UgaGEgZWxpbWluYWRvIGxhIHB1bnR1YWNpw7NuLgotIERlIGZvcm1hIHByZWRldGVybWluYWRhLCBgdW5uZXN0X3Rva2VucygpYCBjb252aWVydGUgbG9zIHRva2VucyBhIG1pbsO6c2N1bGFzLCBsbyBxdWUgbG9zIGhhY2UgbcOhcyBmw6FjaWxlcyBkZSBjb21wYXJhciBvIGNvbWJpbmFyIGNvbiBvdHJvcyBjb25qdW50b3MgZGUgZGF0b3MuIChFc3RvIHB1ZWRlIG1vZGlmaWNhcnNlIHV0aWxpemFuZG8gZWwgYXJndW1lbnRvIGB0b19sb3dlciA9IEZBTFNFYCkKClVuIGRpYWdyYW1hIGRlbCBmbHVqbyBkZSB0cmFiYWpvIHB1ZWRlIHZlcnNlIGEgY29udGludWFjacOzbjoKCiFbXShodHRwczovL3d3dy50aWR5dGV4dG1pbmluZy5jb20vaW1hZ2VzL3Rtd3JfMDEwMS5wbmcpCgoKIyMgT3JkZW5hbmRvIGFsZ3Vub3MgdGV4dG9zIGRlIE1hcnggeSBFbmdlbHMKClZhbW9zIGEgdHJhYmFqYXIgY29uIHVuIGRhdGFzZXQgcXVlIGVsIGNhcG8gZGUgW0RpZWdvIEtvc2xvc2tpXShodHRwczovL3NpdGVzLmdvb2dsZS5jb20vdmlldy9kaWVnby1rb3psb3dza2kvaG9tZSkgZXNjcmFwZcOzIGRlIGxhIHNlY2Npw7NuIGVuIGVzcGHDsW9sIGRlbCBbTWFyeGlzdCBJbnRlcm5ldCBBcmNoaXZlXShodHRwczovL3d3dy5tYXJ4aXN0cy5vcmcvZXNwYW5vbC8pLiAKCkNhcmdhbW9zIGxvcyBkYXRvczoKYGBge3J9Cm1hcnhfZW5nZWxzIDwtIHJlYWRfY3N2KCcuLi9kYXRhL21hcnhfZW5nZWxzLmNzdicpCm1hcnhfZW5nZWxzCmBgYAoKwr9RdcOpIGVzdHJ1Y3R1cmEgdGllbmUgZXN0ZSBkYXRhc2V0PwoKVmFtb3MgYSB0cmFuc2Zvcm1hcmxvIGVuIHVuIGZvcm1hdG8gdGlkeToKCmBgYHtyfQptYXJ4X2VuZ2Vsc190aWR5IDwtIG1hcnhfZW5nZWxzICU+JQogICAgICAgIHVubmVzdF90b2tlbnMod29yZCwgdGV4dG8pCmBgYAoKCmBgYHtyfQptYXJ4X2VuZ2Vsc190aWR5CmBgYAoKIyMgRWxpbWluYW5kbyBzdG9wd29yZHMKRWwgc2lndWllbnRlIHBhc28gZXMgbGEgZWxpbWluYWNpw7NuIGRlIGxhcyBsbGFtYWRhcyBzdG9wd29yZHMuIFNlIHRyYXRhIGRlIHBhbGFicmFzIHF1ZSBvIGJpZW4gcG9yIHN1IGZ1bmNpw7NuIHNpbnTDoWN0aWNhIChwcm9ub21icmVzLCBwcmVwb3NpY2lvbmVzLCBhZHZlcmJpb3MsIGV0Yy4pIG8gcG9yIHN1IGZyZWN1ZW5jaWEgKGFwYXJlY2VuIGVuIGdyYW4gZnJlY3VlbmNpYSkgbm8gYXBvcnRhbiBpbmZvcm1hY2nDs24gYWwgdGV4dG8uCgpFbiBnZW5lcmFsLCBsYSBmb3JtYSBlc3RhbmRhciBkZSBsaWRpYXIgY29uIGxhcyBzdG9wd29yZHMgZXMgbWVkaWFudGUgc3UgZWxpbWluYWNpw7NuIGEgdHJhdsOpcyBkZSB1bmEgbGlzdGEuIENhcmd1ZW1vcyBsYSBsaXN0YSBjb24gbGFzIHN0b3B3b3JkcywgYWwgbWlzbW8gdGllbXBvLCB2YW1vcyBhIGVsaW1pbmFyIGxvcyBhY2VudG9zIGRlIGVzdGEgdGFibGEuCgoKYGBge3J9CnN0b3Bfd29yZHMgPC0gcmVhZF9jc3YoJ2h0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9BbGlyM3o0L3N0b3Atd29yZHMvbWFzdGVyL3NwYW5pc2gudHh0JywgY29sX25hbWVzPUZBTFNFKSAlPiUKICAgICAgICByZW5hbWUod29yZCA9IFgxKSAlPiUKICAgICAgICBtdXRhdGUod29yZCA9IHN0cmluZ2k6OnN0cmlfdHJhbnNfZ2VuZXJhbCh3b3JkLCAiTGF0aW4tQVNDSUkiKSkKYGBgCgpBaG9yYSBzw60sIHBvZGVtb3MgcmVtb3ZlcmxhcyB1c2FuZG8gbGEgZnVuY2lvbiBgYW50aV9qb2luYCAKCmBgYHtyfQptYXJ4X2VuZ2Vsc190aWR5IDwtIG1hcnhfZW5nZWxzX3RpZHkgJT4lCiAgYW50aV9qb2luKHN0b3Bfd29yZHMpCmBgYAoKRsOtamVuc2UgY8OzbW8gcGFzYW1vcyBkZSBhcHJpbWFkYW1lbnRlIDEuMDAwLjAwMCBkZSBwYWxhYnJhcyBhIDUyOS4wMDAgbHVlZ28gZGUgZWxpbWluYXIgbGFzIHN0b3Bfd29yZHMuCgpBaG9yYSBiaWVuLCDCv2N1w6FsZXMgc29uIGxhcyBwYWxhYnJhcyBtw6FzIHVzYWRhcyBwb3IgTWFyeCB5IEVuZ2Vscz8KCmBgYHtyfQptYXJ4X2VuZ2Vsc190aWR5ICU+JQogICAgICAgIGNvdW50KHdvcmQsIHNvcnQ9VFJVRSkKYGBgCgotLS0tLS0tCgojIyMgQWN0aXZpZGFkCgpFc2NyaWJpciBlbCBjw7NkaWdvIHBhcmEgcmVwbGljYXIgbGEgdGFibGEgYW50ZXJpb3IgdXNhbmRvIGxvcyBjb21hbmRvcyBkZWwgYHRpZHl2ZXJzZWAKCmBgYHtyfQojIyMKYGBgCgotLS0tLS0tCgpEZWJpZG8gYSBxdWUgaGVtb3MgZXN0YWRvIHVzYW5kbyBoZXJyYW1pZW50YXMgb3JkZW5hZGFzLCBudWVzdHJvcyByZWN1ZW50b3MgZGUgcGFsYWJyYXMgc2UgYWxtYWNlbmFuIGVuIHVuIG1hcmNvIGRlIGRhdG9zIG9yZGVuYWRvLiBFc3RvIG5vcyBwZXJtaXRlIGNhbmFsaXphciBlc3RvIGRpcmVjdGFtZW50ZSBhbCBwYXF1ZXRlIGBnZ3Bsb3QyYCwgcG9yIGVqZW1wbG8sIHBhcmEgY3JlYXIgdW5hIHZpc3VhbGl6YWNpw7NuIGRlIGxhcyBwYWxhYnJhcyBtw6FzIGNvbXVuZXM6CgpgYGB7cn0KbWFyeF9lbmdlbHNfdGlkeSAlPiUKICAgICAgICBjb3VudCh3b3JkLCBzb3J0PVRSVUUpICU+JQogICAgICAgIGZpbHRlcihuID4gNjAwKSAlPiUKICAgICAgICBtdXRhdGUod29yZCA9IHJlb3JkZXIod29yZCwgbikpICU+JQogICAgICAgIGdncGxvdChhZXMobiwgd29yZCkpICsKICAgICAgICAgICAgICAgIGdlb21fY29sKCkgKwogICAgICAgICAgICAgICAgbGFicyh5ID0gTlVMTCkKCmBgYAoKUG9kcsOtYW1vcyBldmFsdWFyIGFob3JhIHNpIE1hcnggeSBFbmdlbHMgdXNhbiBkaWZlcmVudGVzIHBhbGFicmFzIGVuIGxhcyBjYXJnYXMgeSBub3RhcyB5IGVuIHN1cyBsaWJyb3MuIFBhcmEgZWxsbyB2YW1vcyBhIHRlbmVyIHF1ZSBwcm9jZXNhciB1biBwb2NvIGVsIGNhbXBvIGRlIGB0aXR1bG9gOgoKYGBge3J9Cm1hcnhfZW5nZWxzX3RpZHkgPC0gbWFyeF9lbmdlbHNfdGlkeSAlPiUKICAgICAgICBtdXRhdGUodGlwbyA9IGNhc2Vfd2hlbigKICAgICAgICAgICAgICAgIHN0cl9kZXRlY3QodGl0dWxvLCAnQ2FydGEnKSB+ICdjYXJ0YXMnLAogICAgICAgICAgICAgICAgVFJVRSB+IHRpcG8KICAgICAgICApKSAKYGBgCgpVdGlsaXphbW9zIGxhIGZ1bmNpw7NuIGBzdHJfZGV0ZWN0YCBkZWwgcGFxdWV0ZSBgc3RyaW5ncmAgcGFyYSB0ZXN0ZWFyIHNpIGxhIGNvbmRpY2nDs24gc2UgY3VtcGxlLi4uIGVuIGVzdGUgY2FzbyBzaSBsYSBwYWxhYnJhIGBDYXJ0YWAgYXBhcmVjZSBlbiBsYXMgZmlsYXMgZGUgYHTDrXR1bG9gLgoKYGBge3J9CmZyZXFzIDwtIG1hcnhfZW5nZWxzX3RpZHkgJT4lCiAgICAgICAgbXV0YXRlKHdvcmQgPSBzdHJfZXh0cmFjdCh3b3JkLCAiW2EteiddKyIpKSAlPiUKICAgICAgICBncm91cF9ieSh0aXBvLCB3b3JkKSAlPiUKICAgICAgICBzdW1tYXJpc2UobiA9IG4oKSkgJT4lCiAgICAgICAgbXV0YXRlKAogICAgICAgICAgICAgICAgdG90YWwgPSBzdW0obiksCiAgICAgICAgICAgICAgICBwcm9wID0gbi90b3RhbCoxMDApICU+JQogICAgICAgIHNlbGVjdCh0aXBvLCB3b3JkLCBwcm9wKSAlPiUKICAgICAgICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gdGlwbywgdmFsdWVzX2Zyb20gPSBwcm9wKQoKZnJlcXMKYGBgCgpZIGFob3JhIHBvZGVtb3MgaGFjZXIgdW4gZ3LDoWZpY28gZW4gZWwgcXVlIGNvbXBhcmFtb3MgbGEgZnJlY3VlbmNpYSBkZSB1c28gZGUgbGFzIGRpZmVyZW50ZXMgcGFsYWJyYXMgZW4gbG9zIGxpYnJvcyB5IGxhcyBub3RhczogCgpgYGB7cn0KZnJlcXMgJT4lCmdncGxvdCggYWVzKG5vdGFzLCBsaWJyb3MpKSArCiAgZ2VvbV9qaXR0ZXIoYWxwaGEgPSAwLjA1LCBzaXplID0gMi41LCB3aWR0aCA9IDAuMjUsIGhlaWdodCA9IDAuMjUpICsKICBnZW9tX3RleHQoYWVzKGxhYmVsID0gd29yZCksIGNoZWNrX292ZXJsYXAgPSBUUlVFLCB2anVzdCA9IDEuNSkgKwogIHNjYWxlX3hfbG9nMTAoKSArCiAgc2NhbGVfeV9sb2cxMCgpICsKICBnZW9tX2FibGluZShjb2xvciA9ICJyZWQiKSArCiAgdGhlbWVfbWluaW1hbCgpCmBgYAoKTGFzIHBhbGFicmFzIHF1ZSBlc3TDoW4gY2VyY2EgZGUgbGEgbMOtbmVhIGVuIGVzdG9zIGdyw6FmaWNvcyB0aWVuZW4gZnJlY3VlbmNpYXMgc2ltaWxhcmVzIGVuIGFtYm9zIGNvbmp1bnRvcyBkZSB0ZXh0b3MsIHBvciBlamVtcGxvLCB0YW50byBlbiBsb3MgbGlicm9zIGRlIE1hcnggeSBFbmdlbHMgY29tbyBlbiBsYXMgYXBhcmVjZW4gY29uIGZyZWN1ZW5jaWFzIHNpbWlhbHJlczogYnVyZ3Vlc2EsIGFjY2nDs24sIGNhbXBlc2lub3MsIGFib2xpY2nDs24sIGNvbXVuYSwgcHJvZHVjY2nDs24sIGNsYXNlLiBFbiBjYW1iaW8sIGVuIGxvcyBsaWJyb3MgcGFyZWNlbiBhcGFyZWNlciBwYWxhYnJhcyBjb21vIGVzZW5jaWEsIGNvbmNlcHRvLCBoZWdlbCwgaW5kaWRpdW8sIGZlbm9tZW5vbG9nw61hLCBtaXN0ZXJpby4gRW4gbGFzIG5vdGFzIHBlcmlvZMOtc2l0Y2FzIGFwYXJlY2Ugbm90YWJsZW1lbnRlIHBhbGFicmFzIGxpZ2FkYXMgYSBsYSBhY2Npw7NuIHBvbMOtdGljYTogY29uZ3Jlc28sIGNvbnNlam8sIGludGVybmFjaW9uYWwsIGxpZ2EsIGVzdGF0dXRvcywgZXRjLgoKLS0tLS0tLQoKIyMjIEFjdGl2aWRhZApSZXBldGlyIGVsIGVqZXJjaWNpbyBjb21wYXJhbmRvIGxhcyBjYXJ0YXMgY29uIGxvcyBsaWJyb3MKCmBgYHtyfQojIyMKYGBgCgotLS0tLS0tCgoKCg==